package com.doudou.oauth.config;

import com.doudou.oauth.exception.SimpleWebResponseExceptionTranslator;
import com.doudou.oauth.handler.AutoUserApprovalHandler;
import com.doudou.oauth.request.CustomizeOAuth2RequestValidator;
import com.doudou.oauth.request.SimpleOAuth2RequestFactory;
import com.doudou.oauth.service.CacheAuthorizationCodeServices;
import com.doudou.oauth.service.MyClientDetailsService;
import com.doudou.oauth.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.OAuth2RequestValidator;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.Arrays;

/**
 * @Author: 傻男人
 * @Date: 2020/5/7 15:42
 * @Version: 1.0
 * @Description: 授权服务器AuthorizationServerConfigurerAdapter
 *  需要自定义授权服务器，继承AuthorizationServerConfigurerAdapter
 */
@Configuration
@EnableAuthorizationServer
public   class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Lazy
    private AuthenticationManager authenticationManager;

    @Autowired
    private MyClientDetailsService myClientDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private TokenEnhancer tokenEnhancer;

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    CacheManager cacheManager;

    @Autowired
    private TokenConfig tokenConfig;

    @Autowired
    private RedisTemplate redisTemplate;



    /**
     * 配置客户端详情，客户端详情可以通过数据库查询，（校验哪些客户端可以申请令牌）
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(myClientDetailsService);
    }

    /**
     * 授权服务-端点配置
     * 配置令牌访问端点url，和令牌服务（令牌生成策略，如何生成）
     * /oauth/authorize  AuthorizationEndpoint 授权端点
     * /oauth/token   TokenEndpoint      获取令牌端点（发送post请求，获取token，code=申请的授权码）
     * /oauth/confirm_access  WhitelabelApprovalEndpoint 用户确认授权端点
     * /oauth/error  WhitelabelErrorEndpoint 授权服务错误信息端点
     * /oauth/check_token  CheckTokenEndpoint 用于资源服务访问的令牌解析端点
     * /oauth/token_key  TokenKeyEndpoint 提供公有密钥的端点，如果使用jwt令牌的话
     * 授权端点url应该被spring-security 保护起来，只供授权用户访问
     *
     * new RedisTokenStore(redisConnectionFactory)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer, accessTokenConverter));
        endpoints
                .tokenStore(tokenStore)
                .accessTokenConverter(accessTokenConverter)
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(myUserDetailsService)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET);
        //异常处理
        endpoints.exceptionTranslator(new SimpleWebResponseExceptionTranslator());

        //授权处理器
        endpoints.tokenGranter(tokenConfig.tokenGranter(endpoints.getTokenServices()));

//         授权码服务
        endpoints.authorizationCodeServices(authorizationCodeServices());

        endpoints.requestFactory(auth2RequestFactory());

        //用户授权处理
        endpoints.userApprovalHandler(userApprovalHandler());
        //scope 校验
        endpoints.requestValidator(requestValidator());
    }


    /**
     * 配置令牌访问端点得安全约束
     * 必须设置allowFormAuthenticationForClients 否则没有办法用postman获取token
     * 也需要指定密码加密方式BCryptPasswordEncoder
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
        //允许表单认证
        security.allowFormAuthenticationForClients()
                //公开获取token的url
                .tokenKeyAccess("isAuthenticated()")
                //校验token的合法性
                .checkTokenAccess("permitAll()");

        //异常的处理
        security.addObjectPostProcessor(new ObjectPostProcessor(){
            @Override
            public Object postProcess(Object object) {
                if(object.getClass().getName().equals(ClientCredentialsTokenEndpointFilter.class.getName())){
                    ClientCredentialsTokenEndpointFilter filter = (ClientCredentialsTokenEndpointFilter)object;
                    OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
                    authenticationEntryPoint.setExceptionTranslator(new SimpleWebResponseExceptionTranslator());
                    filter.setAuthenticationEntryPoint(authenticationEntryPoint);
                }
                return object;
            }
        });
    }

    @Bean
    public ApprovalStore approvalStore(){
        TokenApprovalStore approvalStore = new TokenApprovalStore();
        approvalStore.setTokenStore(tokenStore);
        return  approvalStore;
    }

    /**
     * scope 校验
     * @return
     */
    @Bean
    public OAuth2RequestValidator requestValidator() {
        return  new CustomizeOAuth2RequestValidator();
    }

    /**
     * 用户授权处理
     * @return
     */
    @Bean
    public UserApprovalHandler userApprovalHandler() {
        AutoUserApprovalHandler approvalHandler = new AutoUserApprovalHandler();
        approvalHandler.setRequestFactory(auth2RequestFactory());
        approvalHandler.setApprovalStore(approvalStore());
        approvalHandler.setClientDetailsService(myClientDetailsService);
        return approvalHandler;
    }

    @Bean
    @Primary
    public AuthorizationCodeServices authorizationCodeServices() {
        return new CacheAuthorizationCodeServices(redisTemplate);
    }

    @Bean
    public OAuth2RequestFactory auth2RequestFactory(){
        OAuth2RequestFactory requestFactory = new SimpleOAuth2RequestFactory(myClientDetailsService);
        return requestFactory;
    }
}